Tutorial: Using custom logging routines with binary_c-python

In this notebook you’ll learn how to use the custom logging functionality

[1]:
from binarycpython import _binary_c_bindings
from binarycpython.utils.custom_logging_functions import (
    autogen_C_logging_code,
    binary_c_log_code,
    create_and_load_logging_function,
)
from binarycpython.utils.run_system_wrapper import run_system
from binarycpython.utils.functions import temp_dir
from binarycpython import Population

TMP_DIR = temp_dir("notebooks", "notebook_custom_logging", clean_path=True)
VERBOSITY = 2

The custom logging functionality allows us to decide the output of binary_c without modifying the actual sourcecode of binary_c (i.e. editing src/logging/log_every_timestep in binary_c). Rather, we can create a logging routine from within python.

Technically, the following steps are taken:

  • User creates a logging print statement from within python

  • The logging print statement string gets wrapped into a proper c-function by binary_c_log_code

  • The c-function string gets compiled and loaded into memory by create_and_load_logging_function

  • The memory adress of the compiled and loaded print function can now be passed to C

  • binary_c uses the custom print function

The custom logging functionality can be used when running systems via run_system(), via Population.evolve() and Population.evolve_single(), and directly via the API

Within the logging statement we can access information from the stardata object, as well as use logical statements to determine when to log information. What we cannot do, however, is access functions that are not publicly available. For very elaborate printing routines it is still advised to actually hardcode the print statement into binary_c itself.

Usage

There are two methods to create the C-code that will be compiled:

  • Automatically generate the print statement and use the wrapper to generate the full function string, by using autogen_C_logging_code

  • Create your custom print statement and use the wrapper to generate the full function string, by writing out the print statement. Here the logging statement obviously has to be valid C code

[2]:
# generate logging lines. Here you can choose whatever you want to have logged, and with what header
# this generates working print statements
logging_line = autogen_C_logging_code(
    {
        "MY_STELLAR_DATA": ["model.time", "star[0].mass"],
    }
)
print(logging_line)
Printf("MY_STELLAR_DATA %g %g\n",((double)stardata->model.time),((double)stardata->star[0].mass));
[3]:
# You can also decide to `write` your own logging_line, which allows you to write a more complex logging statement with conditionals.
logging_line = 'Printf("MY_STELLAR_DATA time=%g mass=%g\\n", stardata->model.time, stardata->star[0].mass)'
print(logging_line)
Printf("MY_STELLAR_DATA time=%g mass=%g\n", stardata->model.time, stardata->star[0].mass)
[4]:
# Generate the entire 'script' by wrapping the logging line
custom_logging_code = binary_c_log_code(logging_line)
print(custom_logging_code)
#pragma push_macro("Max")
#pragma push_macro("Min")
#undef Max
#undef Min
#include "binary_c.h"

// add visibility __attribute__ ((visibility ("default"))) to it
void binary_c_API_function custom_output_function(struct stardata_t * stardata);
void binary_c_API_function custom_output_function(struct stardata_t * stardata)
{
    // struct stardata_t * stardata = (struct stardata_t *)x;
    Printf("MY_STELLAR_DATA time=%g mass=%g\n", stardata->model.time, stardata->star[0].mass);
}

#undef Max
#undef Min
#pragma pop_macro("Min")
#pragma pop_macro("Max")

Combining the above with e.g. run_system() (see notebook_individual_systems for more examples):

[5]:
# logging statement
logging_line = 'Printf("MY_STELLAR_DATA time=%g mass=%g\\n", stardata->model.time, stardata->star[0].mass)'

# Entire script
custom_logging_code = binary_c_log_code(logging_line)

# Run system
output = run_system(M_1=2, custom_logging_code=custom_logging_code)

# print (abridged) output
print("\n".join(output.splitlines()[:4]))
MY_STELLAR_DATA time=0 mass=2
MY_STELLAR_DATA time=0 mass=2
MY_STELLAR_DATA time=1e-06 mass=2
MY_STELLAR_DATA time=2e-06 mass=2

Using custom logging with the population object

Custom logging can be used for a whole population by setting the print statement (so not the entire logging script) in C_logging_code

[6]:
# Set up population
pop = Population(verbosity=VERBOSITY, tmp_dir=TMP_DIR)

# Set some BSE parameters
pop.set(
    M_1=5
)

# Example logging that prints only if the star is post main-sequence
example_logging_string_post_MS = """
if(stardata->star[0].stellar_type>MS)
{
    Printf("EXAMPLE_POST_MS %30.12e %g %g %g %g %d %d\\n",
        //
        stardata->model.time, // 1

        stardata->star[0].mass, //2
        stardata->previous_stardata->star[0].mass, //3

        stardata->star[0].radius, //4
        stardata->previous_stardata->star[0].radius, //5

        stardata->star[0].stellar_type, //6
        stardata->previous_stardata->star[0].stellar_type //7
  );
};
"""

# Set the logging
pop.set(
    C_logging_code=example_logging_string_post_MS
)
out = pop.evolve_single()

# Print (abridged) output
print('\n'.join(out.splitlines()[:4]))
adding: tmp_dir=/tmp/binary_c_python-david/notebooks/notebook_custom_logging to population_options
adding: M_1=5 to BSE_options
adding: C_logging_code=
if(stardata->star[0].stellar_type>MS)
{
    Printf("EXAMPLE_POST_MS %30.12e %g %g %g %g %d %d\n",
        //
        stardata->model.time, // 1

        stardata->star[0].mass, //2
        stardata->previous_stardata->star[0].mass, //3

        stardata->star[0].radius, //4
        stardata->previous_stardata->star[0].radius, //5

        stardata->star[0].stellar_type, //6
        stardata->previous_stardata->star[0].stellar_type //7
  );
};
 to population_options
Creating the code for the shared library for the custom logging
File/directory /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/custom_logging.c doesn't exist. Can't remove it.
Writing the custom logging code to /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/custom_logging.c
File/directory /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/libcustom_logging_9c7198faf09e4e83975242045e54f95a.so doesn't exist. Can't remove it.
Calling the binary_c config code to get the info to build the shared library
Got options to compile:
        cc = cc
        ccflags = -std=gnu18 -DOPERATING_SYSTEM=linux -DLINUX -DPOSIX -DLARGEFILE_SOURCE -DMEMORY_ALLOCATION_MODEL=MEMORY_ALLOCATION_NATIVE -UALIGNSIZE -fstrict-aliasing -Wstrict-aliasing -g -Wno-sizeof-pointer-div -Wpedantic -Wshadow -Wno-variadic-macros -fstack-protector-all -rdynamic -fsignaling-nans -march=native -mtune=native -frounding-math -fno-stack-protector -ffloat-store -D__ACCURATE_BINARY_C__ -fno-finite-math-only -fasynchronous-unwind-tables  -export-dynamic -O0 -DCPUFREQ=4500 -DBINARY_C_SRC=/home/david/projects/binary_c_root/binary_c/src -DPGO_off -DBINUTILS_VERSION=2.34 -DBFD_2_33 -D_FILE_OFFSET_BITS=64 -D__HAVE_LINK_H -D__HAVE__VA_OPT__ -D__HAVE_GNU_QSORT_R -D__HAVE_NATIVE_EXP10 -D__HAVE_POSIX_FADVISE -DFPU_CONTROL -D__HAVE_ATTRIBUTE___RESTRICT____ -D__HAVE_ATTRIBUTE_ALLOC_SIZE__ -D__HAVE_ATTRIBUTE_AUTO_TYPE__ -D__HAVE_ATTRIBUTE_BUILTIN_EXPECT__ -D__HAVE_ATTRIBUTE_BUILTIN_ISNAN__ -D__HAVE_ATTRIBUTE_CONST__ -D__HAVE_ATTRIBUTE_DEPRECATED__ -D__HAVE_ATTRIBUTE_GNU_PRINTF__ -D__HAVE_ATTRIBUTE_HOT__ -D__HAVE_ATTRIBUTE_MALLOC__ -D__HAVE_ATTRIBUTE_NONNULL__ -D__HAVE_ATTRIBUTE_NORETURN__ -D__HAVE_ATTRIBUTE_PACKED__ -D__HAVE_ATTRIBUTE_PURE__ -D__HAVE_ATTRIBUTE_RETURNS_NONNULL__ -D__HAVE_ATTRIBUTE_UNUSED__ -DGIT_REVISION=6895:20230518:1fb08c5af -DGIT_URL=git@gitlab-dhendriks:binary_c/binary_c.git -DGIT_BRANCH=releases/2.2.4 -D__HAVE_LIBC__ -D__HAVE_LIBCFITSIO__ -D__HAVE_LIBGSL__ -I/home/david/gsl/include -D__HAVE_LIBGSLCBLAS__ -D__HAVE_LIBDL__ -D__HAVE_LIBPTHREAD__ -D__HAVE_LIBUUID__ -D__HAVE_LIBZ__ -D__HAVE_LIBBACKTRACE__ -D__HAVE_LIBBZ2__ -D__HAVE_LIBCDICT__ -D__HAVE_LIBM__ -D__HAVE_LIBRINTERPOLATE__ -D__HAVE_BACKTRACE_H__ -D__HAVE_IEEE754_H__ -D__HAVE_DRAND48__ -D__HAVE_HSEARCH_DATA__ -D__HAVE_MALLOC_H__ -D__HAVE_SETITIMER__ -D__HAVE_HAS_INCLUDE -D__HAVE_PKG_CONFIG__ -D__HAVE_VALGRIND__ -D__SHOW_STARDATA__ -D__DIFF_STARDATA__ -D__HAVE_7Z__ -D__HAVE_BZCAT__ -D__HAVE_ZCAT__ -O0 -shared -D_SEARCH_H
        ld = ld
        libs = -L/home/david/projects/binary_c_root/binary_c/src -L/usr/lib/x86_64-linux-gnu -L/home/david/gsl/lib -L/home/david/projects/binary_c_root/robs_extra_packages/lib -L/home/david/projects/binary_c_root/binary_c/src -lc -lcfitsio -lpthread -lgsl -lgslcblas -lm -ldl -luuid -lz -lbacktrace -lbz2 -lcdict -lrinterpolate -lbinary_c
        inc = -I/home/david/projects/binary_c_root/binary_c -I/home/david/projects/binary_c_root/binary_c/src -I/home/david/projects/binary_c_root/robs_extra_packages/include -I/home/david/projects/binary_c_root/binary_c/src


Building shared library for custom logging with (binary_c.h) on administrator-XPS-15-7590

Executing following command to compile the shared library:
cc -fPIC -std=gnu18 -DOPERATING_SYSTEM=linux -DLINUX -DPOSIX -DLARGEFILE_SOURCE -DMEMORY_ALLOCATION_MODEL=MEMORY_ALLOCATION_NATIVE -UALIGNSIZE -fstrict-aliasing -Wstrict-aliasing -g -Wno-sizeof-pointer-div -Wpedantic -Wshadow -Wno-variadic-macros -fstack-protector-all -rdynamic -fsignaling-nans -march=native -mtune=native -frounding-math -fno-stack-protector -ffloat-store -D__ACCURATE_BINARY_C__ -fno-finite-math-only -fasynchronous-unwind-tables -export-dynamic -O0 -DCPUFREQ=4500 -DBINARY_C_SRC=/home/david/projects/binary_c_root/binary_c/src -DPGO_off -DBINUTILS_VERSION=2.34 -DBFD_2_33 -D_FILE_OFFSET_BITS=64 -D__HAVE_LINK_H -D__HAVE__VA_OPT__ -D__HAVE_GNU_QSORT_R -D__HAVE_NATIVE_EXP10 -D__HAVE_POSIX_FADVISE -DFPU_CONTROL -D__HAVE_ATTRIBUTE___RESTRICT____ -D__HAVE_ATTRIBUTE_ALLOC_SIZE__ -D__HAVE_ATTRIBUTE_AUTO_TYPE__ -D__HAVE_ATTRIBUTE_BUILTIN_EXPECT__ -D__HAVE_ATTRIBUTE_BUILTIN_ISNAN__ -D__HAVE_ATTRIBUTE_CONST__ -D__HAVE_ATTRIBUTE_DEPRECATED__ -D__HAVE_ATTRIBUTE_GNU_PRINTF__ -D__HAVE_ATTRIBUTE_HOT__ -D__HAVE_ATTRIBUTE_MALLOC__ -D__HAVE_ATTRIBUTE_NONNULL__ -D__HAVE_ATTRIBUTE_NORETURN__ -D__HAVE_ATTRIBUTE_PACKED__ -D__HAVE_ATTRIBUTE_PURE__ -D__HAVE_ATTRIBUTE_RETURNS_NONNULL__ -D__HAVE_ATTRIBUTE_UNUSED__ -DGIT_REVISION=6895:20230518:1fb08c5af -DGIT_URL=git@gitlab-dhendriks:binary_c/binary_c.git -DGIT_BRANCH=releases/2.2.4 -D__HAVE_LIBC__ -D__HAVE_LIBCFITSIO__ -D__HAVE_LIBGSL__ -I/home/david/gsl/include -D__HAVE_LIBGSLCBLAS__ -D__HAVE_LIBDL__ -D__HAVE_LIBPTHREAD__ -D__HAVE_LIBUUID__ -D__HAVE_LIBZ__ -D__HAVE_LIBBACKTRACE__ -D__HAVE_LIBBZ2__ -D__HAVE_LIBCDICT__ -D__HAVE_LIBM__ -D__HAVE_LIBRINTERPOLATE__ -D__HAVE_BACKTRACE_H__ -D__HAVE_IEEE754_H__ -D__HAVE_DRAND48__ -D__HAVE_HSEARCH_DATA__ -D__HAVE_MALLOC_H__ -D__HAVE_SETITIMER__ -D__HAVE_HAS_INCLUDE -D__HAVE_PKG_CONFIG__ -D__HAVE_VALGRIND__ -D__SHOW_STARDATA__ -D__DIFF_STARDATA__ -D__HAVE_7Z__ -D__HAVE_BZCAT__ -D__HAVE_ZCAT__ -O0 -shared -D_SEARCH_H -L/home/david/projects/binary_c_root/binary_c/src -L/usr/lib/x86_64-linux-gnu -L/home/david/gsl/lib -L/home/david/projects/binary_c_root/robs_extra_packages/lib -L/home/david/projects/binary_c_root/binary_c/src -lc -lcfitsio -lpthread -lgsl -lgslcblas -lm -ldl -luuid -lz -lbacktrace -lbz2 -lcdict -lrinterpolate -lbinary_c -o /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/libcustom_logging_9c7198faf09e4e83975242045e54f95a.so /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/custom_logging.c -I/home/david/projects/binary_c_root/binary_c -I/home/david/projects/binary_c_root/binary_c/src -I/home/david/projects/binary_c_root/robs_extra_packages/include -I/home/david/projects/binary_c_root/binary_c/src
loading shared library for custom logging
loaded shared library for custom logging.         custom_output_function is loaded in memory at 140419267154666
Removed /tmp/binary_c_python-david/notebooks/notebook_custom_logging/custom_logging/libcustom_logging_9c7198faf09e4e83975242045e54f95a.so
EXAMPLE_POST_MS             1.044142002936e+02 4.99194 4.99194 6.13567 6.13567 2 1
EXAMPLE_POST_MS             1.044572277695e+02 4.98475 4.99194 7.51803 6.13567 2 2
EXAMPLE_POST_MS             1.045002552454e+02 4.98474 4.98475 9.2129 7.51803 2 2
EXAMPLE_POST_MS             1.045432827213e+02 4.98474 4.98474 11.2892 9.2129 2 2

Using custom logging when running directly from the API

When running a system directly with the API we need to manually load the custom logging into memory (via create_and_load_logging_function) and pass the memory address to the binary_c binding via _binary_c_bindings.run_system(argstring, custom_logging_func_memaddr=custom_logging_memaddr)

[7]:
# generate logging lines
logging_line = autogen_C_logging_code(
    {
        "MY_STELLAR_DATA": ["model.time", "star[0].mass"],
    }
)

# Generate code around logging lines
custom_logging_code = binary_c_log_code(logging_line)

# Generate library and get memaddr
custom_logging_memaddr, shared_lib_filename = create_and_load_logging_function(
    custom_logging_code
)

#
m1 = 15.0  # Msun
m2 = 14.0  # Msun
separation = 0  # 0 = ignored, use period
orbital_period = 4530.0  # days
eccentricity = 0.0
metallicity = 0.02
max_evolution_time = 15000
argstring = "binary_c M_1 {0:g} M_2 {1:g} separation {2:g} orbital_period {3:g} eccentricity {4:g} metallicity {5:g} max_evolution_time {6:g}".format(
    m1,
    m2,
    separation,
    orbital_period,
    eccentricity,
    metallicity,
    max_evolution_time,
)
output = _binary_c_bindings.run_system(
    argstring, custom_logging_func_memaddr=custom_logging_memaddr
)

# print (abridged) output
print('\n'.join(output.splitlines()[:4]))
MY_STELLAR_DATA 0 15
MY_STELLAR_DATA 0 15
MY_STELLAR_DATA 1e-06 15
MY_STELLAR_DATA 2e-06 15

Examples of logging strings

Below are some examples of logging strings

Compact object

This logging will print the timestep when the star becomes a compact object. After it does, we change the maximum time to be the current time, effectively terminating the evolution

[8]:
example_logging_string_CO = """
if(stardata->star[0].stellar_type>=NS)
{
    if (stardata->model.time < stardata->model.max_evolution_time)
    {
        Printf("EXAMPLE_LOG_CO %30.12e %g %g %g %g %d %d\\n",
            //
            stardata->model.time, // 1

            stardata->star[0].mass, //2
            stardata->previous_stardata->star[0].mass, //3

            stardata->star[0].radius, //4
            stardata->previous_stardata->star[0].radius, //5

            stardata->star[0].stellar_type, //6
            stardata->previous_stardata->star[0].stellar_type //7
      );
    };
    /* Kill the simulation to save time */
    stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;
};
"""

# Entire script
custom_logging_code = binary_c_log_code(example_logging_string_CO)

# Run system
output = run_system(M_1=10, custom_logging_code=custom_logging_code)

# print (abridged) output
print("\n".join(output.splitlines()[:4]))
SINGLE_STAR_LIFETIME 10 28.4838
EXAMPLE_LOG_CO             2.848380621878e+01 1.33469 9.1865 1.72498e-05 724.338 13 5

Logging mass evolution and the supernova

This logging code prints the mass evolution and the moment the star goes supernova

[9]:
example_logging_string_CO = """
Printf("EXAMPLE_MASSLOSS %30.12e %g %g %g %d %g\\n",
    //
    stardata->model.time, // 1
    stardata->star[0].mass, //2
    stardata->previous_stardata->star[0].mass, //3
    stardata->common.zero_age.mass[0], //4

    stardata->star[0].stellar_type, //5
    stardata->model.probability //6
);
if(stardata->star[0].SN_type != SN_NONE)
{
    if (stardata->model.time < stardata->model.max_evolution_time)
    {
        Printf("EXAMPLE_SN %30.12e " // 1
            "%g %g %g %d " // 2-5
            "%d %d %g %g " // 6-9
            "%g %g\\n", // 10-13

            //
            stardata->model.time, // 1

            stardata->star[0].mass, //2
            stardata->previous_stardata->star[0].mass, //3
            stardata->common.zero_age.mass[0], //4
            stardata->star[0].SN_type, //5

            stardata->star[0].stellar_type, //6
            stardata->previous_stardata->star[0].stellar_type, //7
            stardata->model.probability, //8
            stardata->previous_stardata->star[0].core_mass[ID_core(stardata->previous_stardata->star[0].stellar_type)],           // 9

            stardata->previous_stardata->star[0].core_mass[CORE_CO],     // 10
            stardata->previous_stardata->star[0].core_mass[CORE_He]    // 11
        );
    };
    /* Kill the simulation to save time */
    stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;
};
"""

# Entire script
custom_logging_code = binary_c_log_code(example_logging_string_CO)

# Run system
output = run_system(M_1=20, custom_logging_code=custom_logging_code)

# print (abridged) output
print("\n".join(output.splitlines()[-2:]))
EXAMPLE_MASSLOSS             1.050658946613e+01 1.59451 9.34203 20 13 1
EXAMPLE_SN             1.050658946613e+01 1.59451 9.34203 20 14 13 5 1 6.55453 4.71658 6.55453